/* typehints:start */
import { Application } from "../../application";
/* typehints:end */

import { ClickDetector } from "./click_detector";
import { Signal } from "./signal";
import { globalConfig } from "../../config/global";
import { arrayDeleteValue, waitNextFrame } from "./utils";
import { SOUNDS } from "../sound/interface";
import { createLogger } from "./logging";

const logger = createLogger('modal_dialog')

export class Dialog {
  constructor({ app, title, contentHtml, buttons, type = 'info', closeButton = false }){
    /** @type {Application} */
    this.app = app;
    this.title = title;
    this.contentHtml = contentHtml;
    this.type = type;
    this.buttonIds = buttons;
    this.closeButton = closeButton;

    this.closeRequested = new Signal;
    this.buttonSignals = {};

    for(let i = 0; i < buttons.length; ++i){
      if(G_IS_DEV && globalConfig.debug.disableTimedButtons){
        this.buttonIds[i] = this.buttonIds[i].replace(':timeout', "");
      }

      const buttonId = this.buttonIds[i].split(':')[0];
      this.buttonSignals[buttonId] = new Signal();
    }

    this.clickDetectors = [];

    this.timeouts = [];
  }

  internalButtonHandler(id, ...params){
    if(id !== 'close-button'){
      this.buttonSignals[id].dispatch(...params);
    }
    this.closeRequested.dispatch();
  }

  createElement(){
    const elem = document.createElement('div')
    elem.classList.add('dialog')

    this.dialogElem = document.createElement('div')
    this.dialogElem.classList.add('dialogInner')

    if(this.type){
      this.dialogElem.classList.add(this.type)
    }
    elem.appendChild(this.dialogElem)

    const title = document.createElement('h1')
    title.innerText = this.title;
    title.classList.add('title')
    this.dialogElem.appendChild(title)

    if(this.closeButton){
      this.dialogElem.classList.add('hasCloseButton')
      const closeBtn = document.createElement('button')
      closeBtn.classList.add('closeButton')

      this.trackClicks(closeBtn, () => this.internalButtonHandler('close-button'), {
        applyCssClass: 'pressedSmallElement'
      })

      title.appendChild(closeBtn)
    }

    const content = document.createElement('div');
    content.classList.add('content')
    content.innerHTML = this.contentHtml;
    this.dialogElem.appendChild(content)

    if(this.buttonIds.length > 0){
      const buttons = document.createElement('div')
      buttons.classList.add('buttons')

      for(let i = 0; i < this.buttonIds.length; i++){
        const [buttonId, buttonStyle, buttonText, rawParams] = this.buttonIds[i].split(':');

        const button = document.createElement('button')
        button.classList.add('button')
        button.classList.add('styledButton')
        button.classList.add(buttonStyle)
        button.innerText = buttonText;

        const params = (rawParams || "").split('/')
        const useTimeout = params.indexOf('timeout') >= 0;
        const unclose = params.indexOf('unclose') >= 0;

        if(useTimeout){
          button.classList.add('timedButton');
          const timeout = setTimeout(() => {
            button.classList.remove('timedButton')
            arrayDeleteValue(this.timeouts, timeout)
          }, 3000);
          this.timeouts.push(timeout);
        }

        this.trackClicks(button, () => {
          if(unclose){
            this.buttonSignals[buttonId].dispatch();
          } else {
            this.internalButtonHandler(buttonId)
          }
        });
        buttons.appendChild(button);
      }

      this.dialogElem.appendChild(buttons)
    } else {
      this.dialogElem.classList.add('buttonless')
    }

    this.element = elem;

    return this.element;
  }

  setIndex(index){
    this.element.style.zIndex = index;
  }

  destroy(){
    if(!this.element){
      assert(false, 'dialog重复销毁');
      return;
    }

    for(let i = 0; i < this.clickDetectors.length; ++i){
      this.clickDetectors[i].cleanup();
    }
    this.clickDetectors = [];

    this.element.remove();
    this.element = null;

    for(let i = 0; i < this.timeouts.length; i++){
      clearTimeout(this.timeouts[i])
    }
    this.timeouts = [];
  }

  hide(){
    this.element.classList.add('visible')
  }

  show(){
    this.element.classList.remove('visible');
  }

  trackClicks(elem, handler, args = {}){
    const detector = new ClickDetector(elem, args);
    detector.click.add(handler, this);
    this.clickDetectors.push(detector)
    return detector;
  }
}

export class DialogLoading extends Dialog{
  constructor(app, text = ''){
    super({
      app,
      title: '',
      contentHtml: '',
      buttons: [],
      type: 'loading'
    })

    this.text = text;
  }

  createElement(){
    const elem = document.createElement('div');
    elem.classList.add('dialog')
    elem.classList.add('loadingDialog')
    this.element = elem;

    if(this.text){
      const text = document.createElement('div')
      text.classList.add('text')
      text.innerText = this.text;
      elem.appendChild(text);
    }

    const loader = document.createElement('div')
    loader.classList.add('prefab_LoadingTextWithAnim');
    loader.classList.add('loadingIndicator')
    elem.appendChild(loader)

    return elem;
  }
}
export class DialogGameLoading extends Dialog{
  constructor(app, text = ''){
    super({
      app,
      title: '',
      contentHtml: '',
      buttons: [],
      type: 'loading'
    })

    this.text = text;
  }

  createElement(){
    const elem = document.createElement('div');
    elem.classList.add('dialog')
    elem.classList.add('gameLoadingDialog')
    this.element = elem;

    const loader = document.createElement('div')
    loader.classList.add('prefab_LoadingTextWithAnim');
    loader.classList.add('loadingIndicator')
    elem.appendChild(loader)

    const text = document.createElement('div')
    text.classList.add('prefab_GameHint')
    text.innerText = this.text;
    elem.appendChild(text);

    this.element = elem;

    return elem;
  }
}

export class DialogInput extends Dialog{
  constructor({ app, title, options }){
    let html = `<div class="inputParent">`;

    let inputValue = {};

    options.forEach(({ key, value, text }) => {
      html += `
        <div class="input" data-inputkey="${key}">
          <h4>${text}</h4>
          <input type="text" value="${value}" />
        </div>
      `

      inputValue[key] = value;
    });

    html += '</div>';

    super({
      app,
      title,
      contentHtml: html,
      buttons: ['ok:good:确认'],
      type: 'info',
      closeButton: true
    })

    this.options = options;
    this.inputValue = inputValue;

    this.buttonSignals.confirm = new Signal();
  }

  createElement(){
    const div = super.createElement();
    this.dialogElem.classList.add('inputDialog')

    div.querySelectorAll('[data-inputkey]').forEach(handle => {
      const key = handle.getAttribute('data-inputkey');
      if(!handle){
        logger.error('错误的dialog选项', key);
        return;
      }

      let input = handle.querySelector('input');

      input.addEventListener('keyup', () => {
        // this.buttonSignals[key].dispatch(input.value);
        this.inputValue[key] = input.value;
      })
    })

    this.buttonSignals.ok.add(() => {
      this.buttonSignals.confirm.dispatch(this.inputValue);
    })

    waitNextFrame().then(() => {
      let input = div.querySelectorAll('input')[0];
      input.focus();
      input.select();
    })

    return div;
  }
}

export class DialogOptionInput extends Dialog{
  constructor({ app, title, options }){
    let html = `<div class="inputParent">`;

    let inputValue = {};

    options.inputs.forEach(({ key, value, text }) => {
      html += `
        <div class="input" data-inputkey="${key}">
          <h3>${text}</h3>
          <input type="text" value="${value}" />
        </div>
      `

      inputValue[key] = value;
    });

    html += '</div><div class="optionParent">'

    options.options.forEach(({ value, text, desc = null }) => {
      const descHtml = desc ? `<span class="desc">${desc}</span>` : "";
      html += `
        <div class="option ${value == options.active ? 'active' : ''}"
          data-optionvalue="${value}"
        >
          <span class="title">${text}</span>
          ${descHtml}
        </div>
      `
    });

    html += '</div>'

    let buttons = ['ok:good:确认'];
    if(options.isRemove){
      buttons.unshift('cancel:bad:删除');
    }

    super({
      app,
      title,
      contentHtml: html,
      buttons,
      type: 'info',
      closeButton: true
    })

    this.options = options;
    this.initialOption = options.active;

    this.inputValue = inputValue;
    this.optionActive = options.active;

    this.buttonSignals.confirm = new Signal();
  }

  createElement(){
    const div = super.createElement();
    this.dialogElem.classList.add('optionChooserDialog')
    this.dialogElem.classList.add('inputDialog')

    div.querySelectorAll('[data-inputkey]').forEach(handle => {
      const key = handle.getAttribute('data-inputkey');
      if(!handle){
        logger.error('错误的dialog选项', key);
        return;
      }

      let input = handle.querySelector('input');

      input.addEventListener('keyup', () => {
        // this.buttonSignals[key].dispatch(input.value);
        this.inputValue[key] = input.value;
      })
    })

    div.querySelectorAll('[data-optionvalue]').forEach(handle => {
      const value = handle.getAttribute('data-optionvalue');
      if(!handle){
        logger.error('错误的dialog选项', value);
        return;
      }

      const detector = new ClickDetector(handle, {
        preventDefault: false,
        clickSound: null,
        applyCssClass: 'pressedOption',
      })
      this.clickDetectors.push(detector)

      // if(value !== this.initialOption){
        detector.click.add(() => {
          const selected = div.querySelector('.option.active');
          if(selected){
            selected.classList.remove('active')
          } else {
            // logger.warn('没有默认选中项')
          }
          handle.classList.add('active')
          this.app.sound.playUiSound(SOUNDS.uiClick)
          this.optionActive = value;
          // this.internalButtonHandler('optionSelected', value);
        })
      // }
    })

    this.buttonSignals.ok.add(() => {
      this.buttonSignals.confirm.dispatch(this.inputValue, this.optionActive);
    })

    return div;
  }
}

export class DialogOptionChooser extends Dialog{
  constructor({ app, title, options }){
    let html = `<div class="optionParent">`;
    let data = {};

    options.options.forEach(({ value, text, desc = null }, i) => {
      const descHtml = desc ? `<span class="desc">${desc}</span>` : "";
      data[i] = value;
      html += `
        <div class="option ${value === options.active ? 'active' : ''}"
          data-index="${i}"
        >
          <span class="title">${text}</span>
          ${descHtml}
        </div>
      `
    });

    html += '</div>'

    super({
      app,
      title,
      contentHtml: html,
      buttons: [],
      type: 'info',
      closeButton: true
    })

    this.data = data;
    this.options = options;
    this.initialOption = options.active;

    this.buttonSignals.optionSelected = new Signal();
  }

  createElement(){
    const div = super.createElement();
    this.dialogElem.classList.add('optionChooserDialog')

    div.querySelectorAll('[data-index]').forEach(handle => {
      const index = handle.getAttribute('data-index');
      const value = this.data[index];
      if(!handle){
        logger.error('错误的dialog选项', value);
        return;
      }

      const detector = new ClickDetector(handle, {
        preventDefault: false,
        clickSound: null,
        applyCssClass: 'pressedOption',
      })
      this.clickDetectors.push(detector)

      if(value !== this.initialOption){
        detector.click.add(() => {
          const selected = div.querySelector('.option.active');
          if(selected){
            selected.classList.remove('active')
          }
          handle.classList.add('active')
          this.app.sound.playUiSound(SOUNDS.uiClick)
          this.internalButtonHandler('optionSelected', value);
        })
      }
    })
    return div;
  }
}

export class DialogOptionMultiple extends Dialog{
  constructor({ app, title, options }){
    let html = `<div class="optionParent">`;

    options.options.forEach(({ value, text, desc = null }) => {
      const descHtml = desc ? `<span class="desc">${desc}</span>` : "";
      html += `
        <div class="option"
          data-optionvalue="${value}"
        >
          <span class="title">${text}</span>
          ${descHtml}
        </div>
      `
    });

    html += '</div>'

    super({
      app,
      title,
      contentHtml: html,
      buttons: ['ok:good:确认'],
      type: 'info',
    })

    this.data = {};

    this.buttonSignals.confirm = new Signal();
  }

  createElement(){
    const div = super.createElement();
    this.dialogElem.classList.add('optionChooserDialog')

    div.querySelectorAll('[data-optionvalue]').forEach(handle => {
      const value = handle.getAttribute('data-optionvalue');
      if(!handle){
        logger.error('错误的dialog选项', value);
        return;
      }

      const detector = new ClickDetector(handle, {
        preventDefault: false,
        clickSound: null,
        applyCssClass: 'pressedOption',
      })
      this.clickDetectors.push(detector)

      detector.click.add(() => {
        handle.classList.toggle('active')

        if(handle.classList.contains('active')){
          this.data[value] = true;
        } else {
          delete this.data[value];
        }
        this.app.sound.playUiSound(SOUNDS.uiClick)
        // this.internalButtonHandler('optionSelected', value);
      })
    })

    this.buttonSignals.ok.add(() => {
      this.buttonSignals.confirm.dispatch(Object.keys(this.data));
    })

    return div;
  }
}

export class DialogMultipleItem extends Dialog{
  constructor({ app, title, options }){
    let html = `<div class="multipleParent">`;

    options.items.forEach((item, i) => {
      html += `
        <div class="item" data-edit-index="${i}">
          <div class="closeButton" data-remove-index="${i}"></div>

          ${Object.keys(item).map(key => {
            return `<p>${key}: ${
              options.transform 
                ? options.transform(key, item[key]) : item[key]
            }</p>`
          }).join('')}
        </div>
      `
    })

    html += '</div>'

    super({
      app,
      title,
      contentHtml: html,
      buttons: ['add:success:添加:unclose', 'cancel:bad:取消'],
      type: 'info',
      closeButton: true
    })

    this.options = options;
    this.data = options.items;

    this.buttonSignals.removed = new Signal();
    this.buttonSignals.confirm = new Signal();
  }

  createElement(){
    assert(this.options.confirmDialog, '使用多选模态框时, 没有注册<confirmDialog>函数');

    const div = super.createElement();
    this.dialogElem.classList.add('multipleItemDialog')

    div.querySelectorAll('[data-remove-index]').forEach(handle => {
      let index = handle.getAttribute('data-remove-index');
      this.trackClicks(handle, () => {
        this.data.splice(index, 1);

        this.internalButtonHandler('removed', this.data)
      })
    })

    // TODO 修改需要重构，现在先就这样用，问题是调用时的操作过于复杂，不够优雅
    div.querySelectorAll('[data-edit-index]').forEach(handle => {
      let index = handle.getAttribute('data-edit-index');
      this.trackClicks(handle, () => {
        this.options.confirmDialog(this.data[index]).then(data => {
          this.internalButtonHandler('confirm', data, this.data[index]);
        })
        // confirm.add((inputValue, option) => {
          // this.buttonSignals.confirm.dispatch(inputValue, option, this.data[index]);
        // })
      }, {
        targetOnly: true
      })
    })

    this.buttonSignals.add.add(() => {
      this.options.confirmDialog(null).then(data => {
        this.internalButtonHandler('confirm', data);
      })
    })

    return div;
  }
}

export class DialogWithForm extends Dialog {
  /**
   *
   * @param {object} param0
   * @param {Application} param0.app
   * @param {string} param0.title
   * @param {string} param0.desc
   * @param {array=} param0.buttons
   * @param {string=} param0.confirmButtonId
   * @param {string=} param0.extraButton
   * @param {boolean=} param0.closeButton
   * @param {Array<FormElement>} param0.formElements
   */
  constructor({
    app,
    title,
    desc,
    formElements,
    buttons = ["cancel", "ok:good"],
    confirmButtonId = "ok",
    closeButton = true,
  }) {
    let html = "";
    html += desc + "<br>";
    for (let i = 0; i < formElements.length; ++i) {
      html += formElements[i].getHtml();
    }

    super({
      app,
      title: title,
      contentHtml: html,
      buttons: buttons,
      type: "info",
      closeButton,
    });
    this.confirmButtonId = confirmButtonId;
    this.formElements = formElements;

    this.enterHandler = confirmButtonId;
  }

  internalButtonHandler(id, ...payload) {
    if (id === this.confirmButtonId) {
      if (this.hasAnyInvalid()) {
        this.dialogElem.classList.remove("errorShake");
        waitNextFrame().then(() => {
          if (this.dialogElem) {
            this.dialogElem.classList.add("errorShake");
          }
        });
        this.app.sound.playUiSound(SOUNDS.dialogError);
        return;
      }
    }

    super.internalButtonHandler(id, payload);
  }

  hasAnyInvalid() {
    for (let i = 0; i < this.formElements.length; ++i) {
      if (!this.formElements[i].isValid()) {
        return true;
      }
    }
    return false;
  }

  createElement() {
    const div = super.createElement();

    for (let i = 0; i < this.formElements.length; ++i) {
      const elem = this.formElements[i];
      elem.bindEvents(div, this.clickDetectors);
    }

    waitNextFrame().then(() => {
      this.formElements[0].focus();
    });

    return div;
  }
}

export class ModalDialogs{
  constructor(app){
    /** @type {Application} */
    this.app = app;

    this.dialogParent = null;
    this.dialogStack = [];
  }

  initializeToElement(element){
    assert(element, 'dialog父元素不存在');
    this.dialogParent = element;
  }

  showInfo(title, text, buttons = ['ok:good:确认']){
    const dialog = new Dialog({
      app: this.app,
      title,
      contentHtml: text,
      buttons: buttons,
      type: 'info'
    })
    this.internalShowDialog(dialog);
    
    if(this.app){
      this.app.sound.playUiSound(SOUNDS.dialogOk);
    }

    return dialog.buttonSignals;
  }

  showWarning(title, text, buttons = ["ok:good:确认"]) {
    const dialog = new Dialog({
      app: this.app,
      title,
      contentHtml: text,
      buttons: buttons,
      type: 'warnning'
    })
    this.internalShowDialog(dialog);
    
    if(this.app){
      this.app.sound.playUiSound(SOUNDS.dialogOk);
    }

    return dialog.buttonSignals;
  }

  showUpdateInfo(title, text, buttons = ['ok:good:确认']){
    const dialog = new Dialog({
      app: this.app,
      title,
      contentHtml: text,
      buttons: buttons,
      type: 'info'
    })
    this.internalShowDialog(dialog);
    
    if(this.app){
      this.app.sound.playUiSound(SOUNDS.dialogOk);
    }

    return dialog;
  }

  showOptionChooser(title, options){
    const dialog = new DialogOptionChooser({
      app: this.app,
      title,
      options
    })
    this.internalShowDialog(dialog)
    return dialog.buttonSignals;
  }

  showOptionMultiple(title, options){
    const dialog = new DialogOptionMultiple({
      app: this.app,
      title,
      options
    })
    this.internalShowDialog(dialog)
    return dialog.buttonSignals;
  }

  showInput(title, options){
    const dialog = new DialogInput({
      app: this.app,
      title,
      options
    })
    this.internalShowDialog(dialog)
    return dialog.buttonSignals;
  }

  showOptionInput(title, options){
    const dialog = new DialogOptionInput({
      app: this.app,
      title,
      options
    })
    this.internalShowDialog(dialog);
    return dialog.buttonSignals;
  }

  showMultipleItem(title, options){
    const dialog = new DialogMultipleItem({
      app: this.app,
      title,
      options
    })
    this.internalShowDialog(dialog);
    return dialog.buttonSignals;
  }

  showLoadingDialog(text = ""){
    const dialog = new DialogLoading(this.app, text);
    this.internalShowDialog(dialog)
    return this.closeDialog.bind(this, dialog);
  }

  showGameLoadingDialog(text = ''){
    const dialog = new DialogGameLoading(this.app, text);
    this.internalShowDialog(dialog)
    return this.closeDialog.bind(this, dialog);
  }

  showForm({ title, desc, formElements, buttons, closeButton }){
    const dialog = new DialogWithForm({
      app: this.app,
      title: title || '',
      desc: desc || '',
      formElements: formElements || [],
      buttons: buttons || ['cancel:bad:取消', 'ok:good:' + title],
      closeButton: closeButton || true
    })
    this.internalShowDialog(dialog);
    return dialog.buttonSignals;
  }
  
  /**
   * 
   * @param {Dialog} dialog 
   */
  internalShowDialog(dialog){
    const elem = dialog.createElement();
    dialog.setIndex(this.dialogStack.length);

    if(this.dialogStack.lenght > 0){
      this.dialogStack[this.dialogStack.length - 1].hide();
    }

    this.dialogStack.push(dialog);

    dialog.show();
    dialog.closeRequested.add(this.closeDialog.bind(this, dialog));

    this.dialogParent.appendChild(elem)    

    document.body.classList.toggle('modalDialogActive', this.dialogStack.length > 0);
  }

  closeDialog(dialog){
    dialog.destroy();

    let index = -1;
    for(let i = 0; i < this.dialogStack.length; ++i){
      if(this.dialogStack[i] === dialog){
        index = i;
        break;
      }
    }

    assert(index >= 0, 'dialog不存在');
    this.dialogStack.splice(index, 1);

    if(this.dialogStack.length > 0){
      this.dialogStack[this.dialogStack.length - 1].show();
    }

    document.body.classList.toggle('modalDialogActive', this.dialogStack.length > 0);
  }

  close(){
    for(let i = 0; i < this.dialogStack.length; i++){
      const dialog = this.dialogStack[i];
      dialog.destroy();
    }
    this.dialogStack = [];
  }
}